/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.code;

import com.android.dx.rop.code.SourcePosition;
import com.android.dx.util.FixedSizeList;

/**
 * List of source position entries. This class includes a utility method to
 * extract an instance out of a {@link DalvInsnList}.
 */
public final class PositionList extends FixedSizeList {

	/** {@code non-null;} empty instance */
	public static final PositionList EMPTY = new PositionList(0);

	/**
	 * constant for {@link #make} to indicate that no actual position
	 * information should be returned
	 */
	public static final int NONE = 1;

	/**
	 * constant for {@link #make} to indicate that only line number transitions
	 * should be returned
	 */
	public static final int LINES = 2;

	/**
	 * constant for {@link #make} to indicate that only "important" position
	 * information should be returned. This includes block starts and
	 * instructions that might throw.
	 */
	public static final int IMPORTANT = 3;

	/**
	 * Extracts and returns the source position information out of an
	 * instruction list.
	 * 
	 * @param insns
	 *            {@code non-null;} instructions to convert
	 * @param howMuch
	 *            how much information should be included; one of the static
	 *            constants defined by this class
	 * @return {@code non-null;} the positions list
	 */
	public static PositionList make(DalvInsnList insns, int howMuch) {
		switch (howMuch) {
		case NONE: {
			return EMPTY;
		}
		case LINES:
		case IMPORTANT: {
			// Valid.
			break;
		}
		default: {
			throw new IllegalArgumentException("bogus howMuch");
		}
		}

		SourcePosition noInfo = SourcePosition.NO_INFO;
		SourcePosition cur = noInfo;
		int sz = insns.size();
		PositionList.Entry[] arr = new PositionList.Entry[sz];
		boolean lastWasTarget = false;
		int at = 0;

		for (int i = 0; i < sz; i++) {
			DalvInsn insn = insns.get(i);

			if (insn instanceof CodeAddress) {
				lastWasTarget = true;
				;
				continue;
			}

			SourcePosition pos = insn.getPosition();

			if (pos.equals(noInfo) || pos.sameLine(cur)) {
				continue;
			}

			if ((howMuch == IMPORTANT) && !lastWasTarget) {
				continue;
			}

			cur = pos;
			arr[at] = new PositionList.Entry(insn.getAddress(), pos);
			at++;

			lastWasTarget = false;
		}

		PositionList result = new PositionList(at);
		for (int i = 0; i < at; i++) {
			result.set(i, arr[i]);
		}

		result.setImmutable();
		return result;
	}

	/**
	 * Constructs an instance. All indices initially contain {@code null}.
	 * 
	 * @param size
	 *            {@code >= 0;} the size of the list
	 */
	public PositionList(int size) {
		super(size);
	}

	/**
	 * Gets the element at the given index. It is an error to call this with the
	 * index for an element which was never set; if you do that, this will throw
	 * {@code NullPointerException}.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which index
	 * @return {@code non-null;} element at that index
	 */
	public Entry get(int n) {
		return (Entry) get0(n);
	}

	/**
	 * Sets the entry at the given index.
	 * 
	 * @param n
	 *            {@code >= 0, < size();} which index
	 * @param entry
	 *            {@code non-null;} the entry to set at {@code n}
	 */
	public void set(int n, Entry entry) {
		set0(n, entry);
	}

	/**
	 * Entry in a position list.
	 */
	public static class Entry {

		/** {@code >= 0;} address of this entry */
		private final int address;

		/** {@code non-null;} corresponding source position information */
		private final SourcePosition position;

		/**
		 * Constructs an instance.
		 * 
		 * @param address
		 *            {@code >= 0;} address of this entry
		 * @param position
		 *            {@code non-null;} corresponding source position
		 *            information
		 */
		public Entry(int address, SourcePosition position) {
			if (address < 0) {
				throw new IllegalArgumentException("address < 0");
			}

			if (position == null) {
				throw new NullPointerException("position == null");
			}

			this.address = address;
			this.position = position;
		}

		/**
		 * Gets the address.
		 * 
		 * @return {@code >= 0;} the address
		 */
		public int getAddress() {
			return address;
		}

		/**
		 * Gets the source position information.
		 * 
		 * @return {@code non-null;} the position information
		 */
		public SourcePosition getPosition() {
			return position;
		}
	}
}
